﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace BrainTechLLC
{
    [Serializable]
#if NO_SILVERLIGHT
    [Browsable(true)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
#endif
    [DataContract]
    public partial class Vector4BaseInt : IVector4Int
    {
        [DataMember]
        public int x, y, z, w;

        [XmlIgnore]
        public int X
        {
            get { return x; }
            set
            {
                if (!x.AreEqual(value))
                {
                    OnPropertyChanging("X", x, value);
                    x = value;
                    OnPropertyChanged("X", x, value);
                }
            }
        }
        [XmlIgnore]
        public int Y
        {
            get { return y; }
            set
            {
                if (!y.AreEqual(value))
                {
                    OnPropertyChanging("Y", y, value);
                    y = value;
                    OnPropertyChanged("Y", y, value);
                }
            }
        }

        [XmlIgnore]
        public int Z
        {
            get { return z; }
            set
            {
                if (!z.AreEqual(value))
                {
                    OnPropertyChanging("Z", z, value);
                    z = value;
                    OnPropertyChanged("Z", z, value);
                }
            }
        }

        [XmlIgnore]
        public int W
        {
            get { return w; }
            set
            {
                if (!w.AreEqual(value))
                {
                    OnPropertyChanging("W", w, value);
                    w = value;
                    OnPropertyChanged("W", w, value);
                }
            }
        }

        public float XFloat { get { return (float)x; } }
        public float YFloat { get { return (float)y; } }
        public float ZFloat { get { return (float)z; } }
        public float WFloat { get { return (float)w; } }

        public Vector4BaseInt() { }
        public Vector4BaseInt(Vector4BaseInt vector) { Set(vector.X, vector.Y, vector.Z, vector.W); }
        public Vector4BaseInt(int x1, int y1, int z1, int w1) { Set(x1, y1, z1, w1); }
        public Vector4BaseInt(int x1, int y1, int z1) { Set(x1, y1, z1); }

        public override bool Equals(object obj)
        {
            Vector4BaseInt v = obj as Vector4BaseInt;
            if (v == null) return false;
            return x == v.x && y == v.y && z == v.z && w == v.w;
        }

        public bool Equals(int xCheck, int yCheck, int zCheck)
        {
            return x == xCheck && y == yCheck && z == zCheck;
        }

        public bool IsEmpty()
        {
            return (x == 0) && (y == 0) && (z == 0);
        }

        public bool HasNegativeOrZero()
        {
            return (x <= 0) || (y <= 0) || (z <= 0);
        }

        public bool AnyGreatherThan(IVector4Int v)
        {
            return (x > v.X || y > v.Y || z > v.Z);
        }

        public bool SameIntLocation(IVector4Int v)
        {
            return x == v.X && y == v.Y && z == v.Z && w == v.W;
        }

        public long Unique { get { return (long)x + (((long)y) << 21) + (((long)z) << 42); } }

        public Vector4BaseInt(Vector4BaseInt v1, Vector4BaseInt v2)
        {
            x = v2.x - v1.x;
            y = v2.y - v1.y;
            z = v2.z - v1.z;
            w = v2.w - v1.w;
        }

        [XmlIgnore]
        public int this[int CoordinateIndex]
        {
            get
            {
                switch (CoordinateIndex)
                {
                    case 0: return x;
                    case 1: return y;
                    case 2: return z;
                    case 3: return w;
                }

                return 0;
            }
            set
            {
                switch (CoordinateIndex)
                {
                    case 0: x = value; break;
                    case 1: y = value; break;
                    case 2: z = value; break;
                    case 3: w = value; break;
                }
            }
        }
        public static Vector4BaseInt operator *(Vector4BaseInt V, int Factor)
        {
            return new Vector4BaseInt(V.x * Factor, V.y * Factor, V.z * Factor, V.w * Factor);
        }

        public static Vector4BaseInt operator /(Vector4BaseInt V, int Divider)
        {
            if (Divider == 0) throw new ArgumentException("Divider cannot be 0");
            return new Vector4BaseInt(V.x / Divider, V.y / Divider, V.z / Divider, V.w / Divider);
        }

        public void Set(int x1, int y1, int z1, int w1) { X = x1; Y = y1; Z = z1; W = w1; }
        public void Set(int x1, int y1, int z1) { X = x1; Y = y1; Z = z1; }
        public void Set(IVector4Int vect) { X = vect.X; Y = vect.Y; Z = vect.Z; W = vect.W; }

        public static int Length2D(Vector4BaseInt v) { return v.Length2D(); }
        public static int Length3D(Vector4BaseInt v) { return v.Length3D(); }
        public static int Length4D(Vector4BaseInt v) { return v.Length4D(); }

        public static bool operator ==(Vector4BaseInt o1, Vector4BaseInt o2)
        {
            object oA = (object)o1;
            object oB = (object)o2;
            if (oA == null && oB == null) return true;
            if ((oA == null && oB != null) || (oA != null && oB == null)) return false;
            return (o1.x == o2.x &&
                      o1.y == o2.y &&
                      o1.z == o2.z &&
                      o1.w == o2.w);
        }

        public bool SameIntLocation(IVector4Float other)
        {
            return (int)x == other.XInt &&
                      (int)y == other.YInt &&
                      (int)z == other.ZInt &&
                      (int)w == other.WInt;
        }

        public int XInt { get { return x; } }
        public int YInt { get { return y; } }
        public int ZInt { get { return z; } }
        public int WInt { get { return w; } }

        public static bool operator !=(Vector4BaseInt o1, Vector4BaseInt o2)
        {
            object oA = (object)o1;
            object oB = (object)o2;
            if (oA == null && oB == null) return false;
            if ((oA == null && oB != null) || (oA != null && oB == null)) return true;
            return !(o1.x == o2.x &&
                        o1.y == o2.y &&
                        o1.z == o2.z &&
                        o1.w == o2.w);
        }

        public int Length2D()
        {
            return (int)Math.Sqrt((x * x) + (y * y));
        }

        public int Length3D()
        {
            return (int)Math.Pow((x * x) + (y * y) + (z * z), 0.333333333333333334);
        }

        public int Length4D()
        {
            return (int)Math.Pow((x * x) + (y * y) + (z * z) + (w * w), 0.25);
        }

        public static Vector4BaseInt Normalize2D(Vector4BaseInt v)
        {
            int f = v.Length2D();
            return new Vector4BaseInt(v.X / f, v.Y / f, 0);
        }

        public static Vector4BaseInt Normalize3D(Vector4BaseInt v)
        {
            int f = v.Length3D();
            return new Vector4BaseInt(v.X / f, v.Y / f, v.Z / f);
        }

        public static Vector4BaseInt Normalize4D(Vector4BaseInt v)
        {
            int f = v.Length4D();
            return new Vector4BaseInt(v.X / f, v.Y / f, v.Z / f, v.W / f);
        }

        public Vector4BaseInt Normalize2D()
        {
            int f = this.Length2D();
            this.X = (int)(this.X / f);
            this.Y = (int)(this.Y / f);
            return this;
        }

        public Vector4BaseInt Normalize3D()
        {
            int f = this.Length3D();
            this.X = (int)(this.X / f);
            this.Y = (int)(this.Y / f);
            this.Z = (int)(this.Z / f);
            return this;
        }

        public Vector4BaseInt Normalize4D()
        {
            int f = this.Length4D();
            this.X = (int)(this.X / f);
            this.Y = (int)(this.Y / f);
            this.Z = (int)(this.Z / f);
            this.W = (int)(this.W / f);
            return this;
        }

        public static Vector4BaseInt operator -(Vector4BaseInt v1, Vector4BaseInt v2)
        {
            return new Vector4BaseInt(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z, v1.W - v2.W);
        }

        public static int Dot(Vector4BaseInt v1, Vector4BaseInt v2)
        {
            return (v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z + v1.W * v2.W);
        }

        public static Vector4BaseInt Truncate2D(Vector4BaseInt vec, int max_value)
        {
            if (vec.Length2D() > max_value)
            {
                Vector4BaseInt newVec = new Vector4BaseInt(vec).Normalize2D();
                return newVec * max_value;
            }
            return vec;
        }

        public static Vector4BaseInt Truncate3D(Vector4BaseInt vec, int max_value)
        {
            if (vec.Length3D() > max_value)
            {
                Vector4BaseInt newVec = new Vector4BaseInt(vec).Normalize3D();
                return newVec * max_value;
            }
            return vec;
        }

        public static Vector4BaseInt Truncate4D(Vector4BaseInt vec, int max_value)
        {
            if (vec.Length4D() > max_value)
            {
                Vector4BaseInt newVec = new Vector4BaseInt(vec).Normalize4D();
                return newVec * max_value;
            }
            return vec;
        }

        /// <summary>
        /// Scalar product between two vectors.
        /// </summary>
        /// <param name="V1">First vector.</param>
        /// <param name="V2">Second vector.</param>
        /// <returns>Value resulting from the scalar product.</returns>
        public static int operator |(Vector4BaseInt V1, Vector4BaseInt V2)
        {
            int ScalarProduct = 0;
            for (int i = 0; i < 4; i++) ScalarProduct += V1[i] * V2[i];
            return ScalarProduct;
        }

        /// <summary>
        /// Returns a point resulting from the translation of a specified point.
        /// </summary>
        /// <param name="P">Point to translate.</param>
        /// <param name="V">Vector to apply for the translation.</param>
        /// <returns>Point resulting from the translation.</returns>
        public static Vector4BaseInt operator +(Vector4BaseInt P, Vector4BaseInt V)
        {
            Vector4BaseInt result = new Vector4BaseInt();
            for (int i = 0; i < 4; i++) result[i] = P[i] + V[i];
            return result;
        }

        /// <summary>
        /// Returns the projection of a point on the line defined with two other points.
        /// When the projection is out of the segment, then the closest extremity is returned.
        /// </summary>
        /// <exception cref="ArgumentNullException">None of the arguments can be null.</exception>
        /// <exception cref="ArgumentException">P1 and P2 must be different.</exception>
        /// <param name="Pt">Point to project.</param>
        /// <param name="P1">First point of the line.</param>
        /// <param name="P2">Second point of the line.</param>
        /// <returns>The projected point if it is on the segment / The closest extremity otherwise.</returns>
        public static Vector4BaseInt ProjectOnLine(Vector4BaseInt Pt, Vector4BaseInt P1, Vector4BaseInt P2)
        {
            if (Pt == null || P1 == null || P2 == null) throw new ArgumentNullException("None of the arguments can be null.");
            if (P1.Equals(P2)) throw new ArgumentException("P1 and P2 must be different.");
            Vector4BaseInt VLine = new Vector4BaseInt(P1, P2);
            Vector4BaseInt V1Pt = new Vector4BaseInt(P1, Pt);
            // 3D correct below?
            Vector4BaseInt Translation = VLine * (VLine | V1Pt) / VLine.SquareNorm3D;
            Vector4BaseInt Projection = P1 + Translation;

            Vector4BaseInt V1Pjt = new Vector4BaseInt(P1, Projection);
            int D1 = V1Pjt | VLine;
            if (D1 < 0) return P1;

            Vector4BaseInt V2Pjt = new Vector4BaseInt(P2, Projection);
            int D2 = V2Pjt | VLine;
            if (D2 > 0) return P2;

            return Projection;
        }

        public static int DistanceBetween2D(Vector4BaseInt P1, Vector4BaseInt P2)
        {
            return (int)Math.Sqrt((P1.X - P2.X) * (P1.X - P2.X) + (P1.Y - P2.Y) * (P1.Y - P2.Y));
        }

        public static Vector4BaseInt Add(Vector4BaseInt v1, Vector4BaseInt v2)
        {
            return v1 + v2;
        }

        public static Vector4BaseInt Subtract(Vector4BaseInt v1, Vector4BaseInt v2)
        {
            return v1 - v2;
        }

        public static Vector4BaseInt Multiply(Vector4BaseInt v1, int f)
        {
            return v1 * f;
        }

        public static Vector4BaseInt Divide(Vector4BaseInt v1, int f)
        {
            return v1 / f;
        }

        public override int GetHashCode()
        {
            int HashCode = 0;
            for (int i = 0; i < 4; i++) HashCode += this[i];
            return (int)HashCode;
        }

        public int SquareNorm3D
        {
            get
            {
                int Sum = 0;
                for (int i = 0; i < 3; i++) Sum += this[i] * this[i];
                return Sum;
            }
        }

        public int SquareNorm4D
        {
            get
            {
                int Sum = 0;
                for (int i = 0; i < 4; i++) Sum += this[i] * this[i];
                return Sum;
            }
        }
        [XmlIgnore]
        public int Norm3D
        {
            get { return (int)Math.Sqrt(SquareNorm3D); }
            set
            {
                int N = Norm3D;
                if (N == 0) throw new InvalidOperationException("Cannot set norm for a null vector");
                if (N != value)
                {
                    int Facteur = value / N;
                    for (int i = 0; i < 3; i++) this[i] *= Facteur;
                }
            }
        }
        [XmlIgnore]
        public int Norm4D
        {
            get { return (int)Math.Sqrt(SquareNorm4D); }
            set
            {
                int N = Norm4D;
                if (N == 0) throw new InvalidOperationException("Cannot set norm for a null vector");
                if (N != value)
                {
                    int Facteur = value / N;
                    for (int i = 0; i < 4; i++) this[i] *= Facteur;
                }
            }
        }
    }

    public partial class Vector4BaseInt : INotifyPropertyChanged, INotifyPropertyChanging
    {
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

#if NO_SILVERLIGHT
        [field: NonSerialized]
        public event PropertyChangingEventHandler PropertyChanging;
#else
        [field: NonSerialized]
        public event EventHandler<PropertyChangingEventArgsEx> PropertyChanging;
#endif

        public virtual void OnPropertyChanged(string property, object oldValue, object newValue)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgsEx(property) { OldValue = oldValue, NewValue = newValue });
            }
        }

        public virtual void OnPropertyChanging(string property, object oldValue, object newValue)
        {
            if (PropertyChanging != null)
            {
                PropertyChanging(this, new PropertyChangingEventArgsEx(property) { OldValue = oldValue, NewValue = newValue });
            }
        }
    }
}
